/**
 * 文件名：DdLockServiceImpl.java</br>
 * <p>
 * 版本信息：v1.0</br>
 * 日期：2017年6月26日</br>
 * © 2005-2017 雷技信息科技（上海）有限公司版权所有</br>
 */
package com.logic.landseaserver.service.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.logic.landseaserver.common.util.*;
import com.logic.system.service.impl.ConfigurationService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.logic.common.domain.Code;
import com.logic.common.security.LogicUserDetails;
import com.logic.common.security.SecurityUtils;
import com.logic.landseaserver.common.LandeaConstants;
import com.logic.landseaserver.common.LandeaConstants.DdingConstant;
import com.logic.landseaserver.common.LandseaErrorMessageEnum;
import com.logic.landseaserver.common.config.SendMsgConfiguration;
import com.logic.landseaserver.common.exception.LandseaException;
import com.logic.landseaserver.common.token.AbstractAccessTokenManage;
import com.logic.landseaserver.domain.Contract;
import com.logic.landseaserver.domain.DdingAddRoom;
import com.logic.landseaserver.domain.DdingEventCallbackEvt;
import com.logic.landseaserver.domain.DdingModPwdLog;
import com.logic.landseaserver.domain.DdingProjectConfig;
import com.logic.landseaserver.domain.DdingProjectSync;
import com.logic.landseaserver.domain.DdingPwdCallbackEvt;
import com.logic.landseaserver.domain.DdingRoomSync;
import com.logic.landseaserver.domain.Room;
import com.logic.landseaserver.domain.RoomPwdSmsInfo;
import com.logic.landseaserver.domain.SendMsgObject;
import com.logic.landseaserver.domain.SystemAccessToken;
import com.logic.landseaserver.persistence.read.ContractReadMapper;
import com.logic.landseaserver.persistence.read.DdingModPwdLogReadMapper;
import com.logic.landseaserver.persistence.read.DdingProjectConfigReadMapper;
import com.logic.landseaserver.persistence.read.DdingProjectSyncReadMapper;
import com.logic.landseaserver.persistence.read.DdingRoomSyncReadMapper;
import com.logic.landseaserver.persistence.read.ProjectReadMapper;
import com.logic.landseaserver.persistence.read.RoomReadMapper;
import com.logic.landseaserver.persistence.write.DdingModPwdLogWriteMapper;
import com.logic.landseaserver.persistence.write.DdingProjectConfigWriteMapper;
import com.logic.landseaserver.persistence.write.DdingProjectSyncWriteMapper;
import com.logic.landseaserver.persistence.write.DdingRoomSyncWriteMapper;
import com.logic.landseaserver.service.ICustomer;
import com.logic.landseaserver.service.IDdLockService;
import com.logic.landseaserver.service.bean.DdingSyncBean;
import com.logic.landseaserver.service.callback.IEventCallbackHandler;
import com.logic.landseaserver.service.utils.DdingLockConfig;
import com.logic.landseaserver.ws.dto.CustomerDTO;
import com.logic.landseaserver.ws.dto.ProjectDetailForFrontDTO;
import com.logic.system.AppContext;
import com.logic.system.service.ICodeService;

/**
 * 项目名称：system-server</br>
 * 类名称：DdLockServiceImpl</br>
 * 类描述：云丁锁业务接口</br>
 * 创建人：Aaron</br>
 * 创建时间：2017年6月26日 下午4:51:30</br>
 *
 * @version 1.0
 */
@Service(IDdLockService.BEANID)
public class DdLockServiceImpl extends AbstractAccessTokenManage implements IDdLockService
{
    private static final Logger LOG = LoggerFactory.getLogger(DdLockServiceImpl.class);
    
    /**
     * 接口返回成功
     **/
    public static final int SUCCESS = 0;
    
    /**
     * system_acces_token表主键前缀
     **/
    public static final String TOKEN_ID_PREFIX = "DDING_LOCK_";
    
    /**
     * system_acces_token的description字段
     **/
    public static final String TOKEN_DESC = "云丁门锁";
    
    @Autowired
    private RoomPwdEffectiveTimeManage roomPwdEffectiveTimeManage;
    
    /**
     * 上下文
     **/
    @Autowired
    private AppContext appContext;
    
    @Autowired
    private DdingProjectConfigReadMapper projectConfigReadMapper;
    
    @Autowired
    private DdingProjectConfigWriteMapper projectConfigWriteMapper;
    
    @Autowired
    private ProjectReadMapper projectReadMapper;
    
    @Autowired
    private DdingProjectSyncReadMapper ddingProjectSyncReadMapper;
    
    @Autowired
    private DdingProjectSyncWriteMapper ddingProjectSyncWriteMapper;
    
    @Autowired
    private DdingRoomSyncReadMapper ddingRoomSyncReadMapper;
    
    @Autowired
    private DdingRoomSyncWriteMapper ddingRoomSyncWriteMapper;
    
    @Autowired
    private DdingLockConfig ddingLockConfig;
    
    @Autowired
    private RoomReadMapper roomReadMapper;
    
    @Autowired
    private ContractReadMapper contractReadMapper;
    
    @Autowired
    private ICustomer customer;
    
    @Autowired
    private SendMsgConfiguration sendMsgConfiguration;
    
    @Autowired
    private DdingModPwdLogReadMapper ddingModPwdLogReadMapper;
    
    @Autowired
    private DdingModPwdLogWriteMapper ddingModPwdLogWriteMapper;
    
    @Autowired
    private ICodeService codeService;

    @Autowired
    private ConfigurationService configurationService;
    
    /**
     * [描述]：接口saveDdingProjectConfig</br>
     *
     * @param projectConfigArray 配置数组
     * @return 影响的行数
     * @throws LandseaException
     */
    public int saveDdingProjectConfig(DdingProjectConfig[] projectConfigArray)
        throws LandseaException
    {
        if (ArrayUtils.isEmpty(projectConfigArray))
        {
            return 0;
        }
        
        // 参数检查
        for (DdingProjectConfig projectConfig : projectConfigArray)
        {
            if (null == projectConfig.getProjectId() || StringUtils.isEmpty(projectConfig.getConfigName()))
            {
                throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_SAVE_PROJECT_PARFAM_ERROR);
            }
            
            // 查询项目是否存在
            // 检查合法性
            ProjectDetailForFrontDTO project = projectReadMapper.selectProjectBaseInfo(projectConfig.getProjectId());
            
            if (null == project)
            {
                throw LandseaException.createException(LandseaErrorMessageEnum.CONTRACT_OFFLINE_CREATE_PROJECT_ISNULL);
            }
        }
        
        int result = 0;
        
        try
        {
            for (DdingProjectConfig projectConfig : projectConfigArray)
            {
                DdingProjectConfig tempConfig = projectConfigReadMapper.selectByPrimaryKey(projectConfig);
                if (null == tempConfig)
                {
                    tempConfig = new DdingProjectConfig();
                    tempConfig.setProjectId(projectConfig.getProjectId());
                    tempConfig.setConfigName(projectConfig.getConfigName());
                    tempConfig.setConfigValue(projectConfig.getConfigValue());
                    tempConfig.setConfigDesc("云丁锁接口认证配置");
                    int addResult = projectConfigWriteMapper.insertSelective(tempConfig);
                    result += addResult;
                }
                else
                {
                    // 更新 只更新value
                    tempConfig.setConfigValue(projectConfig.getConfigValue());
                    int updateResult = projectConfigWriteMapper.updateByPrimaryKeySelective(tempConfig);
                    result += updateResult;
                }
            }
            
            return result;
        }
        catch (Exception e)
        {
            LOG.error("saveDdingProjectConfig|save dding project config failed," + Arrays.toString(projectConfigArray),
                e);
            throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_SAVE_PROJECT_CONFIG_ERROR, e);
        }
    }
    
    /**
     * [描述]：接口syncHomeAndRooms</br>
     *
     * @param projectId 项目ID
     * @throws LandseaException
     */
    @Override
    public void syncHomeAndRooms(int projectId)
        throws LandseaException
    {
        // 1.检查项目是否存在,获取相关的配置参数
        DdingSyncBean ddingSyncBean = getSyncProjectInfo(projectId);
        
        // 2.若项目未同步，先同步项目，同步失败流程结束
        if (ddingSyncBean.isNeedSyncProject())
        {
            // 若没抛出异常，则同步成功
            syncProject(ddingSyncBean);
        }
        
        // 3.同步房间
        if (ddingSyncBean.isNeedSyncRoom())
        {
            syncRooms(ddingSyncBean);
        }
    }
    
    /**
     * [简要描述]：获取项目同步信息</br>
     * [详细描述]：参数以及需要同步的数据</br>
     * [作者]：Aaron(2017-06-29)</br>
     *
     * @param projectId 项目id
     * @return 同步Bean
     * @throws LandseaException 朗诗异常
     */
    public DdingSyncBean getSyncProjectInfo(int projectId)
        throws LandseaException
    {
        try
        {
            DdingSyncBean syncBean = new DdingSyncBean();
            
            // 检查合法性
            ProjectDetailForFrontDTO project = projectReadMapper.selectProjectBaseInfo(projectId);
            
            if (null == project)
            {
                throw LandseaException.createException(LandseaErrorMessageEnum.CONTRACT_OFFLINE_CREATE_PROJECT_ISNULL);
            }
            
            syncBean.setProject(project);
            
            // 查询项目同步情况
            DdingProjectSync projectSync = ddingProjectSyncReadMapper.selectByPrimaryKey(projectId);
            
            // 不存在认为没有同步
            if (null == projectSync)
            {
                projectSync = new DdingProjectSync();
                projectSync.setProjectId(projectId);
                projectSync.setSyncState(DdingConstant.SYNCSTATE_NO);
                syncBean.setDdingProjectSyncExists(false);
            }
            else
            {
                syncBean.setDdingProjectSyncExists(true);
            }
            
            // 是否需要同步项目
            boolean isNeedSyncProject = projectSync.getSyncState() == DdingConstant.SYNCSTATE_YES ? false : true;
            syncBean.setNeedSyncProject(isNeedSyncProject);
            syncBean.setProjectSync(projectSync);
            
            // 获取同步云丁相关配置
            Map<String, String> projectConfigMap = queryProjectConfig(projectId);
            syncBean.setClientId(projectConfigMap.get("client_id"));
            syncBean.setClientSecret(projectConfigMap.get("client_secret"));
            
            // 查询需要同步的房间
            List<DdingAddRoom> needSyncRoomList = ddingRoomSyncReadMapper.selectNeedSyncRoomByProjectId(projectId);
            boolean isNeedSyncRoom = CollectionUtils.isEmpty(needSyncRoomList) ? false : true;
            syncBean.setNeedSyncRoom(isNeedSyncRoom);
            syncBean.setNeedSyncRoomList(needSyncRoomList);
            
            // 对needSyncRoomList处理，找出没有同步记录的数据,后面要做插入
            if (isNeedSyncRoom)
            {
                List<Integer> noneSyncRoomRecordList = new ArrayList<Integer>();
                for (DdingAddRoom ddingRoomSync : needSyncRoomList)
                {
                    // 在查询SQL中做了特殊处理，如果没有记录，该字段返回-1
                    if (-1 == ddingRoomSync.getSyncState())
                    {
                        noneSyncRoomRecordList.add(ddingRoomSync.getRoomId());
                        // 对特殊处理的数据再改变回去状态
                        ddingRoomSync.setSyncState(DdingConstant.SYNCSTATE_NO);
                    }
                }
                syncBean.setNoneSyncRoomRecordList(noneSyncRoomRecordList);
            }
            
            return syncBean;
        }
        catch (LandseaException e)
        {
            throw e;
        }
        catch (Exception e)
        {
            LOG.error("syncHomeAndRooms|getProjectInfo failed.", e);
            throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_GET_PROJECTL_ERROR, e);
        }
    }
    
    /**
     * [简要描述]：向云丁系统同步项目</br>
     * [详细描述]：</br>
     * [作者]：Aaron(2017-06-29)</br>
     *
     * @param ddingSyncBean 同步Bean，内涵同步所需的信息
     * @throws LandseaException 异常
     */
    public void syncProject(DdingSyncBean ddingSyncBean)
        throws LandseaException
    {
        ProjectDetailForFrontDTO project = ddingSyncBean.getProject();
        
        // 获取accessToken
        Integer projectId = project.getPrjId();
        String accesstoken = getAccessToken(projectId, ddingSyncBean);
        
        // 封装add_home所需参数
        Map<String, Object> bodyParamMap = new HashMap<String, Object>();
        bodyParamMap.put("access_token", accesstoken);
        // 默认集中式公寓
        bodyParamMap.put("home_type", DdingConstant.HOME_TYPE_FOCUS);
        
        // 国家为空时默认中国
        String country =
            StringUtils.isEmpty(project.getCounty()) ? LandeaConstants.DEFAULT_COUNTRY_NAME : project.getCounty();
        bodyParamMap.put("country", country);
        
        // 城市为空时默认N/A
        String city = StringUtils.isEmpty(project.getCity()) ? LandeaConstants.DEFAULT_NONE : project.getCity();
        bodyParamMap.put("city", city);
        
        // 区域为空时默认N/A
        String zone = StringUtils.isEmpty(project.getArea()) ? LandeaConstants.DEFAULT_NONE : project.getArea();
        bodyParamMap.put("zone", zone);
        
        // 详细地址为空时默认N/A
        String location =
            StringUtils.isEmpty(project.getAddressLine1()) ? LandeaConstants.DEFAULT_NONE : project.getAddressLine1();
        bodyParamMap.put("location", location);
        
        // 小区名保持和详细地址一样，朗诗寓项目无此字段
        bodyParamMap.put("block", location);
        
        // 项目id，保证全局唯一即可
        bodyParamMap.put("home_id", projectId);
        
        // 项目名称
        bodyParamMap.put("home_name", project.getPrjName());
        
        // 描述 填写location字段
        bodyParamMap.put("description", location);
        
        // 发送消息
        // 接口名
        String interfaceName = "add_home";
        SendMsgObject sendMsgObject = new SendMsgObject();
        sendMsgObject.setBodyParamMap(bodyParamMap);
        sendMsgObject.setUrl(StringTools.splitJointPath(ddingLockConfig.getRoot_url(), interfaceName));
        SendHttpMsgHandler.sendPostJsonMessage(sendMsgObject);
        
        // 处理结果
        if (!checkSuccessResult(sendMsgObject))
        {
            throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_ADD_PROJECT_ERROR);
        }
        
        // 更新本地同步状态
        DdingProjectSync ddingProjectSync = ddingSyncBean.getProjectSync();
        try
        {
            ddingProjectSync.setSyncState(DdingConstant.SYNCSTATE_YES);
            ddingProjectSync.setSyncTime(new Date());
            // 存在走更新
            if (ddingSyncBean.isDdingProjectSyncExists())
            {
                ddingProjectSyncWriteMapper.updateByPrimaryKeySelective(ddingProjectSync);
            }
            else
            {
                // 不存在走新增
                ddingProjectSyncWriteMapper.insertSelective(ddingProjectSync);
            }
        }
        catch (Exception e)
        {
            LOG.error("syncProject|save sync result to database failed," + ddingProjectSync.toString(), e);
            throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_SAVE_PROJECT_ERROR, e);
        }
    }
    
    /**
     * [简要描述]：向云丁系统同步房间</br>
     * [详细描述]：</br>
     * [作者]：Aaron(2017-06-29)</br>
     *
     * @param ddingSyncBean 同步Bean，内涵同步所需的信息
     * @throws LandseaException 异常
     */
    public void syncRooms(DdingSyncBean ddingSyncBean)
        throws LandseaException
    {
        ProjectDetailForFrontDTO project = ddingSyncBean.getProject();
        
        // 获取accessToken
        Integer projectId = project.getPrjId();
        String accesstoken = getAccessToken(projectId, ddingSyncBean);
        
        // 封装add_rooms所需参数
        Map<String, Object> bodyParamMap = new HashMap<String, Object>();
        bodyParamMap.put("access_token", accesstoken);
        
        // 项目id，保证全局唯一即可
        bodyParamMap.put("home_id", projectId);
        
        // 封装房间
        List<DdingAddRoom> roomList = ddingSyncBean.getNeedSyncRoomList();
        List<Map<String, Object>> roomBodyList = new ArrayList<Map<String, Object>>();
        Map<String, Object> oneRoomMap = null;
        for (DdingAddRoom room : roomList)
        {
            oneRoomMap = new HashMap<String, Object>();
            oneRoomMap.put("room_id", room.getRoomId());
            // 如果空，就填N/A
            String roomName =
                StringUtils.isEmpty(room.getHouseNum()) ? LandeaConstants.DEFAULT_NONE : room.getHouseNum();
            oneRoomMap.put("room_name", roomName);
            
            // 楼栋号 + 单元号 + 楼层号 + 房间号
            StringBuilder description = new StringBuilder();
            description.append("房间位置：");
            description.append(room.getBuildNum() + "号楼");
            description.append(room.getUnitNum() + "单元");
            description.append(room.getFloorNum() + "层");
            description.append(room.getHouseNum() + "室");
            
            oneRoomMap.put("room_description", description.toString());
            roomBodyList.add(oneRoomMap);
        }
        bodyParamMap.put("rooms", roomBodyList);
        
        // 发送消息
        // 接口名
        String interfaceName = "add_rooms";
        SendMsgObject sendMsgObject = new SendMsgObject();
        sendMsgObject.setBodyParamMap(bodyParamMap);
        sendMsgObject.setUrl(StringTools.splitJointPath(ddingLockConfig.getRoot_url(), interfaceName));
        SendHttpMsgHandler.sendPostJsonMessage(sendMsgObject);
        
        // 处理结果
        if (!checkSuccessResult(sendMsgObject))
        {
            throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_ADD_ROOMS_ERROR);
        }
        
        // 更新数据库,允许部分成功
        List<Integer> noSyncRecordRoomList = ddingSyncBean.getNoneSyncRoomRecordList();
        for (DdingAddRoom room : roomList)
        {
            DdingRoomSync ddingRoomSync = new DdingRoomSync();
            ddingRoomSync.setProjectId(projectId);
            ddingRoomSync.setRoomId(room.getRoomId());
            ddingRoomSync.setSyncState(DdingConstant.SYNCSTATE_YES);
            ddingRoomSync.setSyncTime(new Date());
            
            try
            {
                if (noSyncRecordRoomList.contains(room.getRoomId()))
                {
                    // 走新增
                    ddingRoomSyncWriteMapper.insertSelective(ddingRoomSync);
                }
                else
                {
                    // 更新
                    ddingRoomSyncWriteMapper.updateByPrimaryKeySelective(ddingRoomSync);
                }
            }
            catch (Exception e)
            {
                LOG.error("syncRooms|save sync result to database failed," + ddingRoomSync.toString(), e);
                continue;
            }
        }
    }
    
    /**
     * [简要描述]：查询项目所有配置项</br>
     * [详细描述]：</br>
     * [作者]：Aaron(2017-06-26)</br>
     *
     * @param projectId
     * @return 配置项集合
     */
    public Map<String, String> queryProjectConfig(Integer projectId)
    {
        Map<String, String> resultMap = new HashMap<String, String>();
        
        // 查询项目配置项
        List<DdingProjectConfig> queryList = projectConfigReadMapper.selectByProjectId(projectId);
        
        if (CollectionUtils.isEmpty(queryList))
        {
            return resultMap;
        }
        
        for (DdingProjectConfig config : queryList)
        {
            resultMap.put(config.getConfigName(), config.getConfigValue());
        }
        
        return resultMap;
    }
    
    /**
     * [简要描述]：检查结果是否为SUCCESS</br>
     * [详细描述]：ErrNo为0即为SUCCESS</br>
     * [作者]：Aaron(2017-06-27)</br>
     *
     * @return true：成功 false：失败
     */
    protected boolean checkSuccessResult(SendMsgObject sendMsgObject)
        throws LandseaException
    {
        Map<String, Object> resultMap = sendMsgObject.getResponseMap();
        if (MapUtils.isEmpty(resultMap))
        {
            return false;
        }
        
        int resultCode = (Integer)resultMap.get("ErrNo");
        boolean result = SUCCESS == resultCode;
        
        return result;
    }
    
    /**
     * [描述]：获取accessToken</br>
     *
     * @param projectId 项目Id
     * @return accessToken字符串
     * @throws LandseaException
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = {LandseaException.class, Exception.class})
    public String getAccessToken(Integer projectId, DdingSyncBean ddingSyncBean)
        throws LandseaException
    {
        SystemAccessToken accessToken = super.getAccessToken(getTokenId(projectId), ddingSyncBean);
        return accessToken.getTokenValue();
    }
    
    /**
     * [描述]：获取第三方的accessToken</br>
     *
     * @return
     * @throws LandseaException
     */
    @Override
    protected SystemAccessToken getDestAccessToken(String tokenId, Object extendObject)
        throws LandseaException
    {
        DdingSyncBean ddingSyncBean = (DdingSyncBean)extendObject;
        
        // properties方式获取
        String clientId = ddingSyncBean.getClientId();
        String clientSecret = ddingSyncBean.getClientSecret();
        String rootUrl = ddingLockConfig.getRoot_url();
        
        // 参数检查
        CommonUtils.checkEmpty(clientId, LandseaErrorMessageEnum.DDINGLOCK_CLIENTID_IS_NULL);
        CommonUtils.checkEmpty(clientSecret, LandseaErrorMessageEnum.DDINGLOCK_CLIENTSECRET_IS_NULL);
        CommonUtils.checkEmpty(rootUrl, LandseaErrorMessageEnum.DDINGLOCK_ROOTURL_IS_NULL);
        
        // 封装参数
        Map<String, Object> bodyParamMap = new HashMap<String, Object>();
        bodyParamMap.put("client_id", clientId);
        bodyParamMap.put("client_secret", clientSecret);
        
        // 发送消息
        SendMsgObject sendMsgObject = new SendMsgObject();
        sendMsgObject.setBodyParamMap(bodyParamMap);
        sendMsgObject.setUrl(StringTools.splitJointPath(rootUrl, "access_token"));
        SendHttpMsgHandler.sendPostJsonMessage(sendMsgObject);
        
        // 处理结果
        if (!checkSuccessResult(sendMsgObject))
        {
            throw LandseaException.createException(LandseaErrorMessageEnum.COMMON_GET_ACCESSTOKEN_ERROR);
        }
        
        // 封装SystemAccessToken
        Map<String, Object> resultMap = sendMsgObject.getResponseMap();
        SystemAccessToken accessToken = new SystemAccessToken();
        accessToken.setTokenId(tokenId);
        accessToken.setTokenValue((String)resultMap.get("access_token"));
        int unixExpireTime = (Integer)resultMap.get("expires_time");
        long expireTime = unixExpireTime;
        expireTime = expireTime * 1000;
        // 云丁返回的是Unix时间戳，精确到秒，要转换成毫秒的时间戳
        accessToken.setExpiretime(expireTime);
        accessToken.setDescription(TOKEN_DESC);
        
        return accessToken;
    }
    
    /**
     * [描述]：获取云丁tokenId</br>
     * [详情]：规则：DDING_LOCK_projectId</br>
     *
     * @param param 参数
     * @return tokenId
     */
    @Override
    public String getTokenId(Object param)
    {
        int projectId = (Integer)param;
        return TOKEN_ID_PREFIX + projectId;
    }
    
    /**
     * [描述]：接口getDdingProjectConfig</br>
     *
     * @param projectId 项目id
     * @return
     * @throws LandseaException
     */
    @Override
    public List<DdingProjectConfig> getDdingProjectConfig(Integer projectId)
        throws LandseaException
    {
        // 检查合法性
        ProjectDetailForFrontDTO project = projectReadMapper.selectProjectBaseInfo(projectId);
        
        if (null == project)
        {
            throw LandseaException.createException(LandseaErrorMessageEnum.CONTRACT_OFFLINE_CREATE_PROJECT_ISNULL);
        }
        
        // 查询项目配置项
        List<DdingProjectConfig> queryList = projectConfigReadMapper.selectByProjectId(projectId);
        
        return queryList;
    }
    
    /**
     * [描述]：接口ddingProjectRoomStatistic</br>
     *
     * @param projectId 项目id
     * @return
     */
    @Override
    public Map<String, Object> ddingProjectRoomStatistic(Integer projectId)
    {
        Map<String, Object> resultMap = new HashMap<String, Object>();
        
        int totalRoomCount = 0;
        int totalSyncRoomCount = 0;
        int noneSyncRoomCount = 0;
        int installLockRoomCount = 0;
        int noneLockRoomCount = 0;
        
        try
        {
            // 检查合法性
            ProjectDetailForFrontDTO project = projectReadMapper.selectProjectBaseInfo(projectId);
            
            if (null == project)
            {
                throw LandseaException.createException(LandseaErrorMessageEnum.CONTRACT_OFFLINE_CREATE_PROJECT_ISNULL);
            }
            
            // 查询项目所有房间数量
            totalRoomCount = roomReadMapper.selectProjectRoomCount(projectId);
            
            // 查询已同步的房间数量
            totalSyncRoomCount = ddingRoomSyncReadMapper.selectSyncRoomCountByProjectId(projectId);
            
            // 未同步的房间数量
            noneSyncRoomCount = totalRoomCount - totalSyncRoomCount;
            
            // 查询已安装锁的房间数量
            installLockRoomCount = ddingRoomSyncReadMapper.selectInstallLockRoomCountByProjectId(projectId);
            
            // 查询未安装锁的房间数量
            noneLockRoomCount = totalRoomCount - installLockRoomCount;
        }
        catch (Exception e)
        {
            // 有异常只是记录日志，页面上显示0，不抛出
            LOG.error("ddingProjectRoomStatistic|statistic got a big error,projectId=" + projectId, e);
            
            totalRoomCount = 0;
            totalSyncRoomCount = 0;
            noneSyncRoomCount = 0;
            installLockRoomCount = 0;
            noneLockRoomCount = 0;
        }
        
        resultMap.put("totalRoomCount", totalRoomCount);
        resultMap.put("syncRoomCount", totalSyncRoomCount);
        resultMap.put("noneSyncRoomCount", noneSyncRoomCount);
        resultMap.put("installLockRoomCount", installLockRoomCount);
        resultMap.put("noneLockRoomCount", noneLockRoomCount);
        
        return resultMap;
    }
    
    /**
     * [描述]：接口modifyDoorPassword,由页面管家发起重置密码</br>
     *
     * @param roomId 房间Id
     * @throws LandseaException
     */
    @Override
    public void modifyDoorPassword(Integer roomId)
        throws LandseaException
    {
        try
        {
            // 检查房间是否存在
            Room room = roomReadMapper.selectRoomInfo(roomId);
            
            if (null == room)
            {
                throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_ROOM_NOT_EXISTS_ERROR);
            }
            
            // 查询同步记录
            DdingRoomSync ddingRoomSync = ddingRoomSyncReadMapper.selectByPrimaryKey(roomId);
            
            if (null == ddingRoomSync || DdingConstant.SYNCSTATE_YES != ddingRoomSync.getSyncState())
            {
                // 没有同步过，不能修改密码
                throw LandseaException
                    .createException(LandseaErrorMessageEnum.DDINGLOCK_NONESYNC_CANNOT_MODIFYPWD_ERROR);
            }
            
            if (DdingConstant.SYNCSTATE_YES != ddingRoomSync.getLockState())
            {
                // 没有安装门锁，不能修改密码
                throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_NONE_LOCK_ERROR);
            }
            
            // 查询合同
            Contract contract = contractReadMapper.getByRoomId(roomId);
            if (null == contract)
            {
                LOG.error("门锁密码有效期|modifyDoorPassword|Error|can not find constract with roomId=" + roomId);
                throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_NONE_CONSTRACT_ERROR);
            }
            
            // 存在密码，要调用update
            if (StringUtils.isNotEmpty(ddingRoomSync.getLockPassword()) && null != ddingRoomSync.getPasswordId()
                && ddingRoomSync.getPasswordState().intValue() == DdingConstant.LOCK_PWD_SYNC_STATE_FINISH)
            {
                // 如果密码已经失效了，则要走新增
                Date effectiveEndDate = DateUtil.getDateByUnixTimestemp2(ddingRoomSync.getPwdEffectiveTime());
                long currentLongTime = System.currentTimeMillis();
                if (effectiveEndDate.getTime() <= currentLongTime)
                {
                    // 过期
                    // 新增密码,不改变密码明文
                    addLockPassword(ddingRoomSync, false, null, true);
                }
                else
                {
                    // 未过期
                    // 修改密码
                    updateLockPassword(ddingRoomSync, true, true, null);
                }
                
            }
            else
            {
                // 新增密码,需要改变密码明文
                addLockPassword(ddingRoomSync, true, null, true);
            }
        }
        catch (LandseaException e)
        {
            throw e;
        }
        catch (Exception e)
        {
            LOG.error("modifyDoorPassword|occur a big error,roomId=" + roomId, e);
            throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_MODPWD_ERROR, e);
        }
    }
    
    /**
     * [简要描述]：新增云丁门锁密码</br>
     * [详细描述]：</br>
     * [作者]：Aaron(2017-06-30)</br>
     *
     * @param ddingRoomSync 同步Bean，内涵同步所需的信息
     * @param pwdEndTime 门锁失效时间（禁止设置为过去的时间，不然会失败）,如果为null，则根据账单实际情况走
     * @param isNeedSendSms 是否需要发送短信
     * @throws LandseaException 异常
     */
    @Override
    public void addLockPassword(DdingRoomSync ddingRoomSync, boolean isNeedChangePwdText, Date pwdEndTime,
        boolean isNeedSendSms)
        throws LandseaException
    {
        Integer projectId = ddingRoomSync.getProjectId();
        Integer roomId = ddingRoomSync.getRoomId();
        DdingSyncBean ddingSyncBean = new DdingSyncBean();
        
        // 获取同步云丁相关配置
        Map<String, String> projectConfigMap = queryProjectConfig(projectId);
        ddingSyncBean.setClientId(projectConfigMap.get("client_id"));
        ddingSyncBean.setClientSecret(projectConfigMap.get("client_secret"));
        
        // 获取accessToken
        String accesstoken = getAccessToken(projectId, ddingSyncBean);
        
        // 封装add_rooms所需参数
        Map<String, Object> bodyParamMap = new HashMap<String, Object>();
        bodyParamMap.put("access_token", accesstoken);
        
        // 项目id，保证全局唯一即可
        bodyParamMap.put("home_id", projectId);
        
        // 全局唯一，可以只传room_id，不需要传门锁uuid
        bodyParamMap.put("room_id", roomId);
        
        bodyParamMap.put("uuid", ddingRoomSync.getLockUuid());
        
        // 必须是非管理员密码
        bodyParamMap.put("is_default", DdingConstant.LOCK_PASSWORD_TYPE_NOTADMIN);
        
        String password = null;
        if (isNeedChangePwdText)
        {
            // 生成随机的6位数字密码
            int randomNumber = RandomUtil.getRandom(100000, 999999);
            password = String.valueOf(randomNumber);
            bodyParamMap.put("password", password);
        }
        else
        {
            // 不改变密码时直接获取原始密码
            bodyParamMap.put("password", ddingRoomSync.getLockPassword());
            password = ddingRoomSync.getLockPassword();
        }
        
        // 如果没有传密码，则按照合同账单设置
        // 根据房间设置密码有效期
        Map<String, Object> effectiveTimeMap =
            roomPwdEffectiveTimeManage.sendPasswordEffectiveTime(roomId, bodyParamMap, pwdEndTime);

        // 先更新一次数据库,更新密码、有效期、同步状态及是否发送短信
        try
        {
            DdingRoomSync updateRecord = new DdingRoomSync();
            updateRecord.setRoomId(roomId);
            updateRecord.setLockPassword(password);
            updateRecord.setPwdEffectiveTime((Integer)bodyParamMap.get("permission_end"));
            // 密码同步状态更改为同步中
            updateRecord.setPasswordState(DdingConstant.LOCK_PWD_SYNC_STATE_SYNCING);
            // 需要发短信
            updateRecord.setNeedSendSms(isNeedSendSms);
            if (isNeedSendSms)
            {
                // 发送密码短信中的门锁有效日期
                updateRecord.setExtendInfo(JsonObjectUtils.map2JsonStr(effectiveTimeMap));
            }
            ddingRoomSyncWriteMapper.updateByPrimaryKeySelective(updateRecord);
        }
        catch (Exception e)
        {
            LOG.error("addLockPassword|save password to database failed," + ddingRoomSync.toString(), e);
            throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_MODPWD_ERROR, e);
        }
        
        // 发送消息
        // 接口名
        String interfaceName = "add_password";
        SendMsgObject sendMsgObject = new SendMsgObject();
        sendMsgObject.setBodyParamMap(bodyParamMap);
        sendMsgObject.setUrl(StringTools.splitJointPath(ddingLockConfig.getRoot_url(), interfaceName));
        SendHttpMsgHandler.sendPostJsonMessage(sendMsgObject);
        
        boolean sendResult = checkSuccessResult(sendMsgObject);
        
        // 记录密码修改日志
        int effectiveBegin = (Integer)bodyParamMap.get("permission_begin");
        int effectiveEnd = (Integer)bodyParamMap.get("permission_end");
        saveModifyPasswordLog(roomId, sendResult, effectiveBegin, effectiveEnd);
        
        // 处理结果
        if (!sendResult)
        {
            throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_MODPWD_SYNC_ERROR);
        }
        
        // 返回的密码id
        Integer passwordId = (Integer)sendMsgObject.getResponseMap().get("id");
        // 返回操作serviceId
        String serviceId = (String)sendMsgObject.getResponseMap().get("serviceid");
        
        try
        {
            // 更新passwordId
            DdingRoomSync updateRecord = new DdingRoomSync();
            updateRecord.setRoomId(roomId);
            updateRecord.setPasswordId(passwordId);
            updateRecord.setServiceId(serviceId);
            ddingRoomSyncWriteMapper.updateByPrimaryKeySelective(updateRecord);
        }
        catch (Exception e)
        {
            LOG.error("addLockPassword|save password id to database failed," + ddingRoomSync.toString(), e);
            throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_MODPWD_ERROR, e);
        }
    }

    /**
     * [简要描述]：修改云丁门锁密码</br>
     * [详细描述]：</br>
     * [作者]：Aaron(2017-07-18)</br>
     *
     * @param ddingRoomSync
     * @param needSendSms 是否需要发短信true：需要，false不需要
     * @param needChangepwdText 是否需要修改密码明文，true：需要，false不需要
     * @param pwdEndTime 门锁失效时间（禁止设置为过去的时间，不然会失败）,如果为null，则根据账单实际情况走
     * @throws LandseaException
     */
    public void updateLockPassword(DdingRoomSync ddingRoomSync, boolean needSendSms, boolean needChangepwdText,
        Date pwdEndTime)
        throws LandseaException
    {
        Integer projectId = ddingRoomSync.getProjectId();
        Integer roomId = ddingRoomSync.getRoomId();
        DdingSyncBean ddingSyncBean = new DdingSyncBean();
        
        // 获取同步云丁相关配置
        Map<String, String> projectConfigMap = queryProjectConfig(projectId);
        ddingSyncBean.setClientId(projectConfigMap.get("client_id"));
        ddingSyncBean.setClientSecret(projectConfigMap.get("client_secret"));
        
        // 获取accessToken
        String accesstoken = getAccessToken(projectId, ddingSyncBean);
        
        // 封装add_rooms所需参数
        Map<String, Object> bodyParamMap = new HashMap<String, Object>();
        bodyParamMap.put("access_token", accesstoken);
        
        // 项目id，保证全局唯一即可
        bodyParamMap.put("home_id", projectId);
        
        // 全局唯一，可以只传room_id，不需要传门锁uuid
        bodyParamMap.put("room_id", roomId);
        
        // 门锁uuid
        bodyParamMap.put("uuid", ddingRoomSync.getLockUuid());
        
        // 密码Id
        bodyParamMap.put("password_id", ddingRoomSync.getPasswordId());
        
        // 延长有效期时不需要修改密码的明文
        String password = null;
        if (needChangepwdText)
        {
            // 生成随机的6位数字密码
            int randomNumber = RandomUtil.getRandom(100000, 999999);
            password = String.valueOf(randomNumber);
            bodyParamMap.put("password", password);
        }
        
        // 根据房间设置密码有效期
        Map<String, Object> effectiveTimeMap =
            roomPwdEffectiveTimeManage.sendPasswordEffectiveTime(roomId, bodyParamMap, pwdEndTime);
        
        // 发送消息
        // 接口名
        String interfaceName = "update_password";
        SendMsgObject sendMsgObject = new SendMsgObject();
        sendMsgObject.setBodyParamMap(bodyParamMap);
        sendMsgObject.setUrl(StringTools.splitJointPath(ddingLockConfig.getRoot_url(), interfaceName));
        SendHttpMsgHandler.sendPostJsonMessage(sendMsgObject);
        
        boolean sendResult = checkSuccessResult(sendMsgObject);
        
        // 记录密码修改日志
        int effectiveBegin = (Integer)bodyParamMap.get("permission_begin");
        int effectiveEnd = (Integer)bodyParamMap.get("permission_end");
        
        // 不是管家发起的，不需要记录
        if (needSendSms)
        {
            saveModifyPasswordLog(roomId, sendResult, effectiveBegin, effectiveEnd);
        }
        
        // 处理结果
        if (!sendResult)
        {
            throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_MODPWD_SYNC_ERROR);
        }
        
        // 返回操作serviceId
        String serviceId = (String)sendMsgObject.getResponseMap().get("serviceid");
        
        try
        {
            // 更新数据库
            DdingRoomSync updateRecord = new DdingRoomSync();
            updateRecord.setRoomId(roomId);
            updateRecord.setLockPassword(password);
            updateRecord.setPwdEffectiveTime(effectiveEnd);
            // 密码同步状态更改为同步中
            updateRecord.setPasswordState(DdingConstant.LOCK_PWD_SYNC_STATE_SYNCING);
            updateRecord.setNeedSendSms(needSendSms);
            updateRecord.setServiceId(serviceId);
            
            if (needSendSms)
            {
                // 发送密码短信中的门锁有效日期
                updateRecord.setExtendInfo(JsonObjectUtils.map2JsonStr(effectiveTimeMap));
            }
            
            ddingRoomSyncWriteMapper.updateByPrimaryKeySelective(updateRecord);
        }
        catch (Exception e)
        {
            LOG.error("updateLockPassword|save password to database failed," + ddingRoomSync.toString(), e);
            throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_MODPWD_ERROR, e);
        }
    }
    
    /**
     * [描述]：事件回调</br>
     *
     * @param callbackEvent
     * @throws LandseaException
     */
    @Override
    public void eventCallback(DdingEventCallbackEvt callbackEvent)
        throws LandseaException
    {
        LOG.info("Dding|eventCallback|Request|" + callbackEvent.toString());
        
        try
        {
            // 获取事件类别
            String eventType = callbackEvent.getEvent();
            if (StringUtils.isEmpty(eventType))
            {
                LOG.error("Dding|eventCallback|Error|event is empty.");
                throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_CALLBACK_PARAM_ERROR);
            }
            
            // 根据eventType获取处理组件
            String handlerName = DdingConstant.CALLBACK_EVNET_SERVICE_NAME_PREFIX + eventType;
            Object handerObject = appContext.getBean(handlerName);
            if (null == handerObject)
            {
                LOG.error("Dding|eventCallback|Error|event is wrong.");
                throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_CALLBACK_PARAM_ERROR);
            }
            
            IEventCallbackHandler handler = (IEventCallbackHandler)handerObject;
            // 开始处理
            handler.process(callbackEvent);
        }
        catch (LandseaException e)
        {
            throw e;
        }
        catch (Exception e)
        {
            LOG.error("Dding|eventCallback|Error|handle event failed.", e);
            throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_CALLBACK_PROCESS_ERROR, e);
        }
    }
    
    /**
     * [描述]：密码操作回调</br>
     *
     * @param callbackEvent
     * @throws LandseaException
     */
    @Override
    public void passwordEventCallback(DdingPwdCallbackEvt callbackEvent)
        throws LandseaException
    {
        LOG.info("Dding|passwordEventCallback|Request|" + callbackEvent.toString());
        
        try
        {
            // 获取事件类别
            String service = callbackEvent.getService();
            if (StringUtils.isEmpty(service))
            {
                LOG.error("Dding|passwordEventCallback|Error|service is empty.");
                throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_CALLBACK_PARAM_ERROR);
            }
            
            // 根据eventType获取处理组件
            String handlerName = DdingConstant.CALLBACK_EVNET_SERVICE_NAME_PREFIX + service;
            Object handerObject = appContext.getBean(handlerName);
            if (null == handerObject)
            {
                LOG.error("Dding|passwordEventCallback|Error|service is wrong.");
                throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_CALLBACK_PARAM_ERROR);
            }
            
            IEventCallbackHandler handler = (IEventCallbackHandler)handerObject;
            // 开始处理
            handler.process(callbackEvent);
        }
        catch (LandseaException e)
        {
            throw e;
        }
        catch (Exception e)
        {
            LOG.error("Dding|passwordEventCallback|Error|handle event failed.", e);
            throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_CALLBACK_PROCESS_ERROR, e);
        }
    }
    
    /**
     * [简要描述]：发送修改密码短信</br>
     * [详细描述]：</br>
     * [作者]：Aaron(2017-07-04)</br>
     *
     * @param ddingRoomSync
     * @throws LandseaException
     */
    public void sendPwdMsgToUser(DdingRoomSync ddingRoomSync)
        throws LandseaException
    {
        StringBuilder smsContentStrBuild = new StringBuilder();
        Integer roomId = ddingRoomSync.getRoomId();
        String password = ddingRoomSync.getLockPassword();
        // 通过扩展参数获取短信过期时间
        Integer pwdEffectiveTime = getExpireTimeFromExtendsInfo(ddingRoomSync);
        
        try
        {
            // 查询合同
            RoomPwdSmsInfo roomPwdSmsInfo = contractReadMapper.getSmsInfoByRoomId(roomId);
            if (null == roomPwdSmsInfo)
            {
                LOG.error("Dding|sendPwdMsgToUser|Error|can not find constract with roomId=" + roomId);
                throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_NONE_CONSTRACT_ERROR);
            }
            Integer userId = roomPwdSmsInfo.getUserId();
            String phone = roomPwdSmsInfo.getUserPhone();
            
            // 查询用户
            CustomerDTO customerDto = customer.getCustomerInfo(userId);
            if (null == customer)
            {
                LOG.error("Dding|sendPwdMsgToUser|Error|customer not exists with userId=" + userId);
                throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_NONE_USERINFO_ERROR);
            }
            
            // 尊敬的${name}，欢迎您入住朗诗寓${project}，您的房号是${room}，密码是${pwd}。有效期至${date}。请妥善保管。
            /*smsContentStrBuild.append(" {'name':'");
            smsContentStrBuild.append(roomPwdSmsInfo.getUserName());
            
            Code code = codeService.getCode(customerDto.getGender());
            if (code != null)
            {
                // 如果性别不为空，则要加上"先生"或"女士"
                smsContentStrBuild.append(code.getCodeDescTr());
            }
            */
            smsContentStrBuild.append(" {'project':'");
            smsContentStrBuild.append(roomPwdSmsInfo.getProjectName());
            smsContentStrBuild.append("','room':'");
            smsContentStrBuild.append(roomPwdSmsInfo.getHouseNum());
            smsContentStrBuild.append("','pwd':'");
            smsContentStrBuild.append(password);
            smsContentStrBuild.append("','date':'");
            String dateStr = DateUtil.getDateByUnixTimestemp(pwdEffectiveTime);
            smsContentStrBuild.append(dateStr);
            smsContentStrBuild.append("'}");
            
            LOG.info("sendPwdMsgToUser|sms content|" + smsContentStrBuild.toString());
            
            // 发送短信
            String signName = configurationService.getStringValue(ConfigurationService.SEND_MSG_SIGN_NAME);
            SMSUtil.sendSms(sendMsgConfiguration.getModifyLockPwdCode(), phone, smsContentStrBuild.toString(),signName);
            
            // 不以接收到门锁密码为依据来判断入住，交割单首次保存做
            // if (result) {
            // //将房间状态改为已出租
            // Room dsRoom = new Room();
            // dsRoom.setId(roomId);
            // dsRoom.setAppendStatus(RoomStatusCodeEnum.LRB3.getCode());
            // iProjectAppartment.updateAppendStatus(dsRoom);
            // }
        }
        catch (LandseaException e)
        {
            throw e;
        }
        catch (Exception e)
        {
            LOG.error("sendPwdMsgToUser|send sms to user falied." + smsContentStrBuild.toString(), e);
            throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_SEND_SMS_ERROR, e);
        }
    }
    
    /**
     * [简要描述]：保存修改门锁密码日志</br>
     * [详细描述]：</br>
     * [作者]：Aaron(2017-07-05)</br>
     *
     * @param roomId 房间id
     * @param sendResult 修改结果
     * @param startTime 密码有效期开始
     * @param endTime 密码有效期结束
     * @throws LandseaException
     */
    public void saveModifyPasswordLog(Integer roomId, boolean sendResult, int startTime, int endTime)
        throws LandseaException
    {
        DdingModPwdLog record = new DdingModPwdLog();
        try
        {
            record.setStartTime(DateUtil.getDateByUnixTimestemp2(startTime));
            record.setEndTime(DateUtil.getDateByUnixTimestemp2(endTime));
            record.setResult(sendResult);
            LogicUserDetails currentUser = SecurityUtils.getCurrentLogin();
            record.setModifyAuthor(currentUser.getUserName());
            record.setModifyDate(new Date());
            record.setRoomId(roomId);
            ddingModPwdLogWriteMapper.insertSelective(record);
        }
        catch (Exception e)
        {
            LOG.error("saveModifyPasswordLog|save password log to database failed" + record.toString(), e);
            throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_MODPWD_LOG_ERROR, e);
        }
    }
    
    /**
     * [描述]：获取云丁修改门锁密码日志</br>
     *
     * @param roomId 房间ID
     * @return List<DdingModPwdLog>
     * @throws LandseaException
     */
    @Override
    public List<DdingModPwdLog> getModifyPwdLog(Integer roomId)
        throws LandseaException
    {
        try
        {
            List<DdingModPwdLog> list = ddingModPwdLogReadMapper.selectByRoomId(roomId);
            
            if (null == list)
            {
                return new ArrayList<DdingModPwdLog>();
            }
            
            return list;
        }
        catch (Exception e)
        {
            LOG.error("getModifyPwdLog query error.", e);
            throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_GET_MODPWD_LOG_ERROR, e);
        }
    }
    
    /**
     * [描述]：重置密码的有效期</br>
     *
     * @param roomId 房间Id
     * @param contractId 合同id
     * @param pwdEndTime 门锁失效时间（禁止设置为过去的时间，不然会失败）,如果为null，则根据账单实际情况走
     * @throws LandseaException
     */
    @Override
    public void resetPasswordEffectiveTime(Integer roomId, Integer contractId, Date pwdEndTime)
    {
        LOG.info("resetPasswordEffectiveTime|Start reset password effective time,roomId=" + roomId + ",contractId="
            + contractId + "Time=" + pwdEndTime);
        
        try
        {
            // 如果roomId，根据合同id去查询
            if (null == roomId)
            {
                // 查询
                Contract contract = contractReadMapper.selectByPrimaryKey(contractId);
                if (null == contract || contract.getDeleted())
                {
                    LOG.error(
                        "Dding|resetPasswordEffectiveTime|Error|can not find constract with contractId=" + contractId);
                    throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_NONE_CONSTRACT_ERROR);
                }
                
                roomId = contract.getRoomId();
            }
            
            // 检查房间是否存在
            Room room = roomReadMapper.selectRoomInfo(roomId);
            
            if (null == room)
            {
                throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_ROOM_NOT_EXISTS_ERROR);
            }
            
            // 查询同步记录
            DdingRoomSync ddingRoomSync = ddingRoomSyncReadMapper.selectByPrimaryKey(roomId);
            
            if (null == ddingRoomSync || DdingConstant.SYNCSTATE_YES != ddingRoomSync.getSyncState())
            {
                // 没有同步过，不能修改密码
                throw LandseaException
                    .createException(LandseaErrorMessageEnum.DDINGLOCK_NONESYNC_CANNOT_MODIFYPWD_ERROR);
            }
            
            if (DdingConstant.SYNCSTATE_YES != ddingRoomSync.getLockState())
            {
                // 没有安装门锁，不能修改密码
                throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_NONE_LOCK_ERROR);
            }
            
            // 存在密码，要调用update
            //if (StringUtils.isNotEmpty(ddingRoomSync.getLockPassword()) && null != ddingRoomSync.getPasswordId())
            // 只要密码ID存在就行
            if (null != ddingRoomSync.getPasswordId())
            {
                // 如果密码已经失效了，则要走新增
                Date effectiveEndDate = DateUtil.getDateByUnixTimestemp2(ddingRoomSync.getPwdEffectiveTime());
                long currentLongTime = System.currentTimeMillis();
                if (effectiveEndDate.getTime() <= currentLongTime)
                {
                    // 过期
                    // 新增密码,但明文不变,不需要发送短信
                    addLockPassword(ddingRoomSync, false, pwdEndTime, false);
                }
                else
                {
                    // 未过期
                    // 修改密码,只是延长密码有效期
                    updateLockPassword(ddingRoomSync, false, false, pwdEndTime);
                }
            }
            else
            {
                // 本方法只更新密码的有效期，如果没有设置过密码，记录日志退出
                LOG.error(
                    "resetPasswordEffectiveTime|this room has not password and can not reset effecive time,roomId="
                        + roomId);
            }
        }
        catch (LandseaException e)
        {
            LOG.error("resetPasswordEffectiveTime|occur a big error,roomId=" + roomId + "|" + e.getErrorMsg(), e);
        }
        catch (Exception e)
        {
            LOG.error("resetPasswordEffectiveTime|occur a big error,roomId=" + roomId, e);
        }
    }
    
    /**
     * [描述]：</br>
     *
     * @param projectId
     * @return
     * @throws LandseaException
     *
     */
    @Override
    public Map<String, Object> checkProjectConfigExists(Integer projectId)
        throws LandseaException
    {
        Map<String, String> projectConfigMap = queryProjectConfig(projectId);
        
        LOG.info("checkProjectConfigExists|query result|" + projectConfigMap);
        
        Map<String, Object> resultMap = new HashMap<String, Object>();
        
        boolean isExists = false;
        
        if (MapUtils.isEmpty(projectConfigMap))
        {
            isExists = false;
            resultMap.put("exists", isExists);
            return resultMap;
        }
        
        Collection<String> coll = projectConfigMap.values();
        for (String value : coll)
        {
            if (StringUtils.isNotEmpty(value))
            {
                isExists = true;
                break;
            }
        }
        
        resultMap.put("exists", isExists);
        
        return resultMap;
    }
    
    /**
     * 
     * [简要描述]：获取发送短信的门锁有效截至日期</br>
     * [详细描述]：</br>
     * [作者]：Aaron(2017-09-07)</br>
     *
     * @param ddingRoomSync
     * @return
     * @throws LandseaException
     *
     */
    private Integer getExpireTimeFromExtendsInfo(DdingRoomSync ddingRoomSync)
        throws LandseaException
    {
        String extendInfo = ddingRoomSync.getExtendInfo();
        if (StringUtils.isEmpty(extendInfo))
        {
            return ddingRoomSync.getPwdEffectiveTime();
        }
        
        Map<String, Object> map = JsonObjectUtils.jsonStr2map(extendInfo);
        Integer sendSmsEndTime = (Integer)map.get("sendSmsEndTime");
        if (null == sendSmsEndTime)
        {
            return ddingRoomSync.getPwdEffectiveTime();
        }
        
        return sendSmsEndTime;
    }
    
    /**
     * [描述]：</br>
     *
     * @param roomId
     * @return
     * @throws LandseaException
     *
     */
    @Override
    public String getRoomPwdEffectiveTime(Integer roomId)
        throws LandseaException
    {
        DdingRoomSync roomSync = ddingRoomSyncReadMapper.selectByPrimaryKey(roomId);
        if (null == roomSync)
        {
            return StringUtils.EMPTY;
        }
        
        Integer pwdEffectiveTime = roomSync.getPwdEffectiveTime();
        String dateStr = DateUtil.getDateByUnixTimestemp(pwdEffectiveTime);
        return dateStr;
    }



    @Override
    public void updateSynRoomByPrjId(Integer prjId) throws LandseaException {
        DdingSyncBean ddingSyncBean = new DdingSyncBean();

        // 获取同步云丁相关配置
        Map<String, String> projectConfigMap = queryProjectConfig(prjId);
        ddingSyncBean.setClientId(projectConfigMap.get("client_id"));
        ddingSyncBean.setClientSecret(projectConfigMap.get("client_secret"));
        // 获取accessToken
        String accesstoken = getAccessToken(prjId, ddingSyncBean);

        // 封装add_home所需参数
        Map<String, Object> bodyParamMap = new HashMap<String, Object>();
        bodyParamMap.put("access_token", accesstoken);
        // 默认集中式公寓
        bodyParamMap.put("home_id",prjId);

        // 发送消息
        // 接口名
        String interfaceName = "get_home_info";
        StringBuffer url = new StringBuffer();
        url.append(StringTools.splitJointPath(ddingLockConfig.getRoot_url(), interfaceName));
        url.append("?access_token="+accesstoken);
        url.append("&home_id="+prjId);
        String result = HttpRequestClient.getRequest(url.toString(),null);
        // 结果为json字符串，需要将字符串转换成Map以便处理
        Map<String, Object> resultMap = JsonObjectUtils.jsonStr2map(result);
        if (MapUtils.isEmpty(resultMap))
        {
            throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_ADD_PROJECT_ERROR);
        }

        int resultCode = (Integer)resultMap.get("ErrNo");
        if( SUCCESS != resultCode){
            throw LandseaException.createException(LandseaErrorMessageEnum.DDINGLOCK_NONE_RESPONSE_FOR_GETHOMEINFO);
        }
        //获取返回的房间信息
        String  resultInfo = (String)resultMap.get("result");
        JSONObject resutJson = JSONObject.parseObject(resultInfo);
        JSONArray devices = resutJson.getJSONArray("devices");

        // 更新数据库,允许部分成功
        // 查询需要同步的房间
        List<DdingAddRoom> needSyncRoomList = ddingRoomSyncReadMapper.selectNeedSyncRoomByProjectId(prjId);

        for (DdingAddRoom room : needSyncRoomList)
        {
            try{
                for (int i = 0; i < devices.size(); i++) {
                    JSONObject jsonObject = devices.getJSONObject(i);
                    Integer rId  = jsonObject.getInteger("room_id");
                    if(room.getRoomId() == rId){
                        DdingRoomSync ddingRoomSync = new DdingRoomSync();
                        ddingRoomSync.setProjectId(prjId);
                        ddingRoomSync.setRoomId(room.getRoomId());
                        ddingRoomSync.setSyncState(DdingConstant.SYNCSTATE_YES);
                        ddingRoomSync.setSyncTime(new Date());
                        ddingRoomSync.setLockState(DdingConstant.LOCK_INSTALL_STATE_SUCCESS);
                        ddingRoomSync.setInstallLockTime(new Date());
                        ddingRoomSync.setLockUuid(jsonObject.getString("uuid"));
                        // 更新
                        ddingRoomSyncWriteMapper.updateByPrimaryKeySelective(ddingRoomSync);
                    }
                }
            } catch (Exception e){
                LOG.error("syncRooms|save sync result to database failed," + room.getRoomId(), e);
                continue;
            }
        }
    }
}
